import Foundation

fileprivate class FLInnerStates {
    var children: [FLNode] = []
    weak var parent: FLNode?
    weak var view: FLView!

    // add at 11.06
    // (width & height都是exactly时) 可以进行计算，TODO: remove isDirty
    var initExactly = false
    var didMeasured = false
    var updatedMeasureSizeByParent = false
    var prepareToCompute = false
    var computeParentBasisBySelf = false
    // 用于正式compute时判断parentDimensions是否发生变化
    var cachedDimensionsForComputeParentBasis : FLDimensions?
    
    
    
    // inner states
    let isRoot: Bool
    let rootBounds: CGRect
    var isLeaf = true
    // TODO: remove
    var hadLayout = false
    var isDirty = true
    var crossLine = 0
    var measuredDimensions: FLDimensions?
    // TODO: check, 作为过度值考虑删除
    var resolvedDimensions: FLDimensions?
    var crossLineComputedFlex: FLComputeFlex?
    var computedParentFlexSize: (Width: Float, Height: Float)?
    
    var relativeChildren: [FLNode] {
        return children.filter { it -> Bool in
            it.display == .flex && it.position == .relative
        }
    }
    
    var allCrossLineComputedFlex: [FLComputeFlex] {
        if children.count > 0 {
            var currCrossLine = 0
            var all: [FLComputeFlex] = [children.first!.innerState.crossLineComputedFlex!]
            for child in children {
                if child.innerState.crossLine > currCrossLine {
                    all.append(child.innerState.crossLineComputedFlex!)
                    currCrossLine = child.innerState.crossLine
                }
            }
            return all
        } else {
            return []
        }
    }

    init(view: FLView, isRoot: Bool) {
        self.isRoot = isRoot
        self.view = view
        if self.isRoot {
            self.rootBounds = self.view.bounds
        } else {
            self.rootBounds = CGRect.zero
        }
    }
    
    func markDirty() {
        if !isDirty {
            isDirty = true
            if let parent = self.parent {
                parent.innerState.markDirty()
            }
        }
    }
}

class FLNode : Hashable, Equatable {
    fileprivate var innerState: FLInnerStates!
    // TODO: remove
    fileprivate var cachedInnerState: FLInnerStates?
    // REMARK: properties for item
    var order: UInt = 0
    var flexBasis: FLValue = 0
    var flexShrink: FLValue = 0
    var flexGrow: FLValue = 0
    var alignSelf: FLAlign = .auto
    
    // REMARK: properties for container
    var paddingLeft: FLValue = 0
    var paddingRight: FLValue = 0
    var paddingTop: FLValue = 0
    var paddingBottom: FLValue = 0
    var marginLeft: FLValue = 0
    var marginRight: FLValue = 0
    var marginTop: FLValue = 0
    var marginBottom: FLValue = 0

    var left: FLValue = .undefined
    var right: FLValue = .undefined
    var top: FLValue = .undefined
    var bottom: FLValue = .undefined

    var justify: FLJustify = .start
    var alignItems: FLAlign = .stretch
    var alignContent: FLAlign = .start
    var wrap: FLWrap = .noWrap

    var position: FLPosition = .relative
    var direction : FLDirection = .column
    var display: FLDisplay = .flex

    var minWidth: FLValue = .undefined
    var minHeight: FLValue = .undefined
    var maxWidth: FLValue = .undefined
    var maxHeight: FLValue = .undefined
    var width: FLValue = .undefined
    var height: FLValue = .undefined
    
    // REMARK: properties for layout
    var isMainAxisRow : Bool {
        return direction == .row || direction == .rowReverse
    }
    
    var isMainAxisDirectionReverse : Bool {
        return isMainAxisRow ? direction == .rowReverse : direction == .columnReverse
    }
    
    var mainAxisDirection: FLAxisDirection {
        return isMainAxisRow ? .axisRow : .axisColumn
    }
    
    var paddingForAxisRow : Float {
        return paddingLeft.value + paddingRight.value
    }
    
    var paddingForAxisColumn : Float {
        return paddingTop.value + paddingBottom.value
    }
    
    var marginForAxisRow : Float {
        return marginLeft.value + marginRight.value
    }
    
    var marginForAxisColumn : Float {
        return marginTop.value + marginBottom.value
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(self.innerState.view)
        for child in self.innerState.children {
            hasher.combine(child.innerState.view)
        }
        hasher.combine(self.width)
        hasher.combine(self.minWidth)
        hasher.combine(self.maxWidth)
        hasher.combine(self.height)
        hasher.combine(self.minHeight)
        hasher.combine(self.maxHeight)
        hasher.combine(self.position)
        hasher.combine(self.direction)
        hasher.combine(self.display)
        hasher.combine(self.justify)
        hasher.combine(self.alignSelf)
        hasher.combine(self.alignContent)
        hasher.combine(self.wrap)
        hasher.combine(self.flexBasis)
        hasher.combine(self.flexGrow)
        hasher.combine(self.flexShrink)
        hasher.combine(self.alignSelf)
        hasher.combine(self.order)
        hasher.combine(self.paddingLeft)
        hasher.combine(self.paddingRight)
        hasher.combine(self.paddingTop)
        hasher.combine(self.paddingBottom)
        hasher.combine(self.marginLeft)
        hasher.combine(self.marginRight)
        hasher.combine(self.marginTop)
        hasher.combine(self.marginBottom)
    }
    
    static func == (lhs: FLNode, rhs: FLNode) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }
}

extension FLNode : CustomDebugStringConvertible {
    var debugDescription: String {
        return "node.view is \(self.innerState.view!), didMeasured: \(self.innerState.didMeasured), prepareToCompute: \(self.innerState.prepareToCompute)"
    }
    
    convenience init(_ view: FLView, isRoot: Bool) {
        self.init()
        innerState = FLInnerStates(view: view, isRoot: isRoot)
    }
    
    var padding : Float {
        get {
            fatalError("You cannot read from this object.")
        }
        set {
            paddingLeft = .numeric(newValue)
            paddingRight = .numeric(newValue)
            paddingTop = .numeric(newValue)
            paddingBottom = .numeric(newValue)
        }
    }
    
    var paddingHorizontal : Float {
        get {
            fatalError("You cannot read from this object.")
        }
        set {
            paddingLeft = .numeric(newValue)
            paddingRight = .numeric(newValue)
        }
    }
    
    var paddingVertical : Float {
        get {
            fatalError("You cannot read from this object.")
        }
        set {
            paddingTop = .numeric(newValue)
            paddingBottom = .numeric(newValue)
        }
    }
    
    var margin : Float {
        get {
            fatalError("You cannot read from this object.")
        }
        set {
            marginLeft = .numeric(newValue)
            marginRight = .numeric(newValue)
            marginTop = .numeric(newValue)
            marginBottom = .numeric(newValue)
        }
    }
    
    var marginHorizontal : Float {
        get {
            fatalError("You cannot read from this object.")
        }
        set {
            marginLeft = .numeric(newValue)
            marginRight = .numeric(newValue)
        }
    }
    
    var marginVertical : Float {
        get {
            fatalError("You cannot read from this object.")
        }
        set {
            marginTop = .numeric(newValue)
            marginBottom = .numeric(newValue)
        }
    }
        
    fileprivate func setParent(node: FLNode) {
        innerState.parent = node
    }
    
    fileprivate func addChild(node: FLNode) {
        innerState.isLeaf = false
        innerState.children.append(node)
    }
    
    func markDirty(forAllNode: Bool=false) {
        if forAllNode || self.innerState.view!.isFLContentNode {
            self._markDirty()
            
            for child in self.innerState.children {
                child.markDirty(forAllNode: forAllNode)
            }
        }
    }
    
    func _markDirty() {
        if !(self.innerState.isRoot && self.innerState.didMeasured) {
            self.innerState.view.frame = .zero
        }
        self.innerState.cachedDimensionsForComputeParentBasis = nil
        self.innerState.computedParentFlexSize = nil
        self.innerState.computeParentBasisBySelf = false
        self.innerState.crossLine = 0
        self.innerState.crossLineComputedFlex = nil
        self.innerState.didMeasured = false
        self.innerState.prepareToCompute = false
        self.innerState.initExactly = false
        self.innerState.isDirty = true
        self.innerState.measuredDimensions = nil
        self.innerState.resolvedDimensions = nil

        if let parent = self.innerState.parent {
            parent._markDirty()
        }
    }
    
    func layoutCenter() {
        justify = .center
        alignItems = .center
    }
    
    func matchAbsoluteParent() {
        left = 0
        right = 0
        top = 0
        bottom = 0
    }
}

class FLComputeFlex : CustomDebugStringConvertible {
    let crossLine: Int
    var totalFlexShrinkScaledFactors: Float = 0
    var totalFlexGrowFactors: Float = 0
    var remainingFreeSpace: Float = 0
    var deltaFlexShrinkScaledFactors: Float = 0
    var deltaFlexGrowFactors: Float = 0
    var deltaFreeSpace: Float = 0
    var childFlexBasis: Float = 0
    var flexShrinkScaledFactor: Float = 0
    var flexGrowFactor: Float = 0
    var mainContentDim: Float = 0
    var crossContentDim: Float = 0
    
    var debugDescription: String {
        return "computedFlex on line: \(crossLine), mainDim: \(mainContentDim), crossDim: \(crossContentDim)"
    }
    
    init(crossLine: Int) {
        self.crossLine = crossLine
    }
}

extension FLComputeFlex : Hashable, Equatable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(crossLine)
        hasher.combine(totalFlexShrinkScaledFactors)
        hasher.combine(totalFlexGrowFactors)
        hasher.combine(remainingFreeSpace)
        hasher.combine(deltaFlexShrinkScaledFactors)
        hasher.combine(deltaFlexGrowFactors)
        hasher.combine(deltaFreeSpace)
        hasher.combine(childFlexBasis)
        hasher.combine(flexShrinkScaledFactor)
        hasher.combine(flexGrowFactor)
        hasher.combine(mainContentDim)
        hasher.combine(crossContentDim)
    }
    
    static func == (lhs: FLComputeFlex, rhs: FLComputeFlex) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }
}

//////////////////////////////////////////////////

class FLContext : PropsStatesContext {
    private var localStates: StatesStore! = nil
    private var allNodes: [FLView : FLNode] = [:]
    private var absoluteNodeList: Set<FLNode> = []
    private var emptyParentNodes: [FLNode] = []
    private var cachedNodesHash: [FLView : Int] = [:]
    private weak var rootNode: FLNode! = nil
    private (set) var didRender = false
    private var startObserver = false
    @PropState(autoReset: true)
    var willRenderTrigger = false
    @PropState(autoReset: true)
    var didRenderTrigger = false
    
    func allowObserver() {
        if !self.startObserver {
            self.localStates = StatesStore()
            self.bindStates()
            self.startObserver = true
        }
    }
    
    func getNode(by tag: Int) -> FLNode? {
        guard let view = self.getFirstView(by: tag) else {
            return nil
        }
        return self.getNode(by: view)
    }
    
    func getNode(by view: FLView) -> FLNode? {
        return allNodes[view]
    }
    
    func getFirstView(by tag: Int) -> FLView? {
        for view in self.allNodes.keys {
        #if os(OSX)
            if view.flTag == tag {
                return view
            }
        #else
            if view.tag == tag {
                return view
            }
        #endif
        }
        return nil
    }
    
    func associate(view: FLView, superView: FLView?=nil, isRoot: Bool=false) -> FLNode {
        let node = FLNode(view, isRoot: isRoot)
        allNodes[view] = node
        if isRoot {
            rootNode = node
        }
        if let superView = superView {
            superView.addSubview(view)
            if let parent = allNodes[superView] {
                node.setParent(node: parent)
                parent.addChild(node: node)
            } else {
                emptyParentNodes.append(node)
            }
        }
        // find empty parent nodes
        var index = -1
        var findIndexes = [Int]()
        for child in emptyParentNodes {
            index += 1
            if let superview = child.innerState.view.superview,
                superview == node.innerState.view {
                findIndexes.append(index)
                child.setParent(node: node)
                node.addChild(node: child)
            }
        }
        for index in findIndexes.reversed() {
            emptyParentNodes.remove(at: index)
        }
        return node
    }

    func removeNode(by view: FLView, ignoreParent: Bool=false) {
        var notRemoveFromSuperview = false
        if let node = allNodes[view] {
            if !ignoreParent, let parent = node.innerState.parent {
                parent.innerState.children.removeAll { item in
                    item.innerState.view! == view
                }
            }

            for child in node.innerState.children {
                removeNode(by: child.innerState.view, ignoreParent: false)
            }
            
            if node.innerState.isRoot && ignoreParent {
                notRemoveFromSuperview = true
            }
        }
        if !notRemoveFromSuperview {
            allNodes.removeValue(forKey: view)
            view.removeFromSuperview()
        }
    }
}

extension FLContext {
    func willRenderView() {
        if self.startObserver {
            self.$willRenderTrigger.update(value: true, self.localStates)
        }
    }
    
    // REMARK: Entrance Method
    func apply(containerView: FLView?=nil) {
        self.didRender = false
        if cachedNodesHash.count > 0 {
            var needUpdate = false
            for (_, node) in allNodes {
                if let preHashValue = cachedNodesHash[node.innerState!.view!],
                    node.hashValue != preHashValue {
                    needUpdate = true
                    break
                }
            }
            if needUpdate {
                for (_, node) in allNodes {
                    node._markDirty()
                }
            }
        }
        cachedNodesHash.removeAll()
        for (_, node) in allNodes {
            cachedNodesHash[node.innerState!.view!] = node.hashValue
        }

        // handle absolute node
        absoluteNodeList.removeAll()
        for (_, node) in allNodes {
            if node.position == .absolute, !absoluteNodeList.contains(node) {
                absoluteNodeList.insert(node)
            }
        }
        // make parent dimensions for root node
        let rootViewBounds = rootNode.innerState.rootBounds
        var rootWidthMeasure: FLMeasure = .exactly
        var rootHeightMeasure: FLMeasure = .exactly
        var rootWidth: CGFloat!
        var rootHeight: CGFloat!
        var windowSize: CGSize!
        #if os(OSX)
            windowSize = NSApplication.shared.windows.first!.frame.size
        #else
            windowSize = UIScreen.main.bounds.size
        #endif
        if rootViewBounds.size.width > 0 {
            rootWidth = rootViewBounds.size.width
        } else if rootNode.width.hasValue {
            rootWidth = CGFloat(rootNode.width.value)
        } else {
            rootWidth = windowSize.width
            rootWidthMeasure = .atMost
        }
        
        if rootViewBounds.size.height > 0 {
            rootHeight = rootViewBounds.size.height
        } else if rootNode.height.hasValue {
            rootHeight = CGFloat(rootNode.height.value)
        } else {
            rootHeight = windowSize.height
            rootHeightMeasure = .atMost
        }
        let windowDimensions = FLDimensions(direction: .column,
                                            width: FLValue(rootWidth),
                                            widthMeasure: rootWidthMeasure,
                                            height: FLValue(rootHeight),
                                            heightMeasure: rootHeightMeasure)
        let justForFlexBasis = !windowDimensions.isMeasuredExactly
        computeNodesDimensions(by: rootNode,
                               parentDimensions: windowDimensions,
                               justForFlexBasis: justForFlexBasis)
        // render nodes
        let scale = FLView.scaleOfScreen()
        render(with: rootNode, origin: (0, 0), parentRenderOrigin: FLRenderOrigin.empty, scale: scale)
        // render absolute nodes
        for node in absoluteNodeList {
            if node.display == .none {
                continue
            }
            renderAbsolute(with: node, scale: scale)
        }
        // handle LeftBottom of origin on MacOSX
        #if os(OSX)
        handleLeftBottomOrigin(by: rootNode)
        #endif
        self.didRender = true
        if self.startObserver {
            self.$didRenderTrigger.update(value: true, self.localStates)
        }
    }

    // REMARK: handle layout methods
    private func render(with node: FLNode,
                        origin: (x: Float, y: Float),
                        parentRenderOrigin: FLRenderOrigin,
                        scale: Float) {
        node.innerState.isDirty = false
        layout(with: node, origin: origin, renderOrigin: parentRenderOrigin)
        if node.innerState.isRoot
            || node.position == .absolute {
            let frame = node.innerState.view.frame
            let updateFrame = roundToPixelGrid(with: frame, scale: scale)
            if frame != updateFrame {
                node.innerState.view.frame = updateFrame
            }
        }
        let children = node.innerState.relativeChildren
        if children.count == 0 {
            return
        }
        var renderOrigin = resovleBaseOrigin(by: node)
        let allComputedComputedFlex = node.innerState.allCrossLineComputedFlex
        var currCrossLine = 0
        var currOrigin: (x: Float, y: Float) = renderOrigin.baseOrigin
        for child in children {
            if child.innerState.crossLine != currCrossLine {
                // update next crossLine start origin
                let lastComputedFlex = allComputedComputedFlex[currCrossLine]
                renderOrigin.updateOriginForNextCrossLine(by: node, crossContentDim: lastComputedFlex.crossContentDim)
                currOrigin = renderOrigin.baseOrigin
                currCrossLine += 1
            }
            render(with: child, origin: currOrigin, parentRenderOrigin: renderOrigin, scale: scale)
            // need to use accumulative value of origin to do round
            let frame = child.innerState.view.frame
            let updateFrame = roundToPixelGrid(with: frame, scale: scale)
            if frame != updateFrame {
                child.innerState.view.frame = updateFrame
            }
            // accumulative origin of main axis
            currOrigin = renderOrigin.nextItemOriginInCrossLine(currVew: child.innerState.view!,
                                                                currNode: child)
        }
        // handle align
        if node.innerState.children.count > 0 {
            // first, for mainAxis of wrap mode or crossAxis of noWrap mode
            var items: [(Width: Float, Height: Float, PredicateFn: (FLNode) -> Bool)]!
            if node.wrap == .noWrap {
                items = children.map { item in
                    let measureSize = item.innerState.measuredDimensions!.toSize()
                    return (
                        Width: measureSize.Width,
                        Height: measureSize.Height,
                        PredicateFn: { childNode in childNode == item }
                    )
                }
            } else {
                items = allComputedComputedFlex.map { computedFlex in
                    return (
                        Width: node.isMainAxisRow ? computedFlex.mainContentDim : computedFlex.crossContentDim,
                        Height: node.isMainAxisRow ? computedFlex.crossContentDim : computedFlex.mainContentDim,
                        PredicateFn: { childNode in childNode.innerState.crossLine == computedFlex.crossLine }
                    )
                }
            }
            for contentInfo in items {
                var (needUpdate, deltaX, deltaY) = resolveAlignmentDelta(by: node,
                                                                         contentSize: (contentInfo.Width,
                                                                                       contentInfo.Height),
                                                                         forCrossAxis: node.wrap == .noWrap)
                if !renderOrigin.onOriginLeft {
                    deltaX = deltaX * -1
                }
                if !renderOrigin.onOriginTop {
                    deltaY = deltaY * -1
                }
                if needUpdate {
                    for child in children {
                        if contentInfo.PredicateFn(child) {
                            currOrigin = child.innerState.view.frame.origin.toFloatValue()
                            let frame = child.innerState.view.frame
                            let updateFrame = CGRect(x: currOrigin.x + deltaX,
                                                     y: currOrigin.y + deltaY,
                                                     width: Float(frame.size.width),
                                                     height: Float(frame.size.height))
                            child.innerState.view.frame = updateFrame
                        }
                    }
                }
            }
            // second, for mainAxis of noWrap mode or crossAxis of wrap mode
            var contentDim: Float = 0
            var contentSize: (Width: Float, Height: Float)!
            if node.wrap == .noWrap {
                contentDim = children.first?.innerState.crossLineComputedFlex?.mainContentDim ?? 0
                contentSize = (
                    Width: node.isMainAxisRow ? contentDim : 0,
                    Height: !node.isMainAxisRow ? contentDim : 0
                )
            } else {
                for computedFlex in allComputedComputedFlex {
                    contentDim += computedFlex.crossContentDim
                }
                contentSize = (
                    Width: node.isMainAxisRow ? 0 : contentDim,
                    Height: !node.isMainAxisRow ? 0 : contentDim
                )
            }
            var (needUpdate, deltaX, deltaY) = resolveAlignmentDelta(by: node,
                                                                     contentSize: contentSize,
                                                                     forCrossAxis: node.wrap != .noWrap)
            if !renderOrigin.onOriginLeft {
                deltaX = deltaX * -1
            }
            if !renderOrigin.onOriginTop {
                deltaY = deltaY * -1
            }
            if needUpdate {
                for child in children {
                    currOrigin = child.innerState.view.frame.origin.toFloatValue()
                    let frame = child.innerState.view.frame
                    let updateFrame = CGRect(x: currOrigin.x + deltaX,
                                             y: currOrigin.y + deltaY,
                                             width: Float(frame.size.width),
                                             height: Float(frame.size.height))
                    child.innerState.view.frame = updateFrame
                }
            }
        }
    }

    private func renderAbsolute(with node: FLNode, scale: Float) {
        if node.innerState.measuredDimensions != nil {
            return
        }
        if let parentBounds = node.innerState.parent?.innerState.measuredDimensions?.toSize() {
            // first, resolve size
            let parentDimensions = FLDimensions(direction: .column,
                                                width: FLValue(parentBounds.Width),
                                                widthMeasure: .exactly,
                                                height: FLValue(parentBounds.Height),
                                                heightMeasure: .exactly)
            var measureSizeUpdated = false
            let currDimensions = resolveSize(by: node, parentDimensions: parentDimensions)
            node.innerState.resolvedDimensions = currDimensions
            if !node.width.hasValue
                && node.left.hasValue && node.right.hasValue {
                let left = node.left.value
                let right = node.right.value
                var width: Float = 0
                if (left > 0 && right > 0)
                    && (left + right + node.marginForAxisRow < parentBounds.Width) {
                    width = parentBounds.Width - left - right - node.marginForAxisRow
                } else if (left < 0 && right < 0)
                        && (-left + -right + parentBounds.Width - node.marginForAxisRow) > 0 {
                    width = parentBounds.Width + -left + -right - node.marginForAxisRow
                } else if (left < 0 && right > 0)
                        && (-left + parentBounds.Width - node.marginForAxisRow - right) > 0 {
                    width = -left + parentBounds.Width - node.marginForAxisRow - right
                } else if (left > 0 && right < 0)
                        && (-right + parentBounds.Width - node.marginForAxisRow - left) > 0 {
                    width = -right + parentBounds.Width - node.marginForAxisRow - left
                }
                currDimensions.widthAxis.dim = FLValue(width)
                currDimensions.widthAxis.measure = .exactly
                measureSizeUpdated = true
            }
            if !node.height.hasValue
                && node.top.hasValue && node.bottom.hasValue {
                let top = node.top.value
                let bottom = node.bottom.value
                var height: Float = 0
                if (top > 0 && bottom > 0)
                    && (top + bottom + node.marginForAxisRow < parentBounds.Width) {
                    height = parentBounds.Height - top - bottom - node.marginForAxisRow
                } else if (top < 0 && bottom < 0)
                        && (-top + -bottom + parentBounds.Width - node.marginForAxisRow) > 0 {
                    height = parentBounds.Height + -top + -bottom - node.marginForAxisRow
                } else if (top < 0 && bottom > 0)
                        && (-top + parentBounds.Height - node.marginForAxisRow - bottom) > 0 {
                    height = -top + parentBounds.Height - node.marginForAxisRow - bottom
                } else if (top > 0 && bottom < 0)
                        && (-bottom + parentBounds.Height - node.marginForAxisRow - top) > 0 {
                    height = -bottom + parentBounds.Height - node.marginForAxisRow - top
                }
                currDimensions.heightAxis.dim = FLValue(height)
                currDimensions.heightAxis.measure = .exactly
                measureSizeUpdated = true
            }
            let boundSize = boundAxisSize(withNode: node,
                                          availableSize: currDimensions.toSize(),
                                          parentSize: parentBounds)
            if boundSize.Width != currDimensions.widthAxis.dim.value {
                currDimensions.widthAxis.dim = FLValue(boundSize.Width)
                currDimensions.widthAxis.measure = .exactly
                measureSizeUpdated = true
            }
            if boundSize.Height != currDimensions.heightAxis.dim.value {
                currDimensions.heightAxis.dim = FLValue(boundSize.Height)
                currDimensions.heightAxis.measure = .exactly
                measureSizeUpdated = true
            }
            // add at 12.20
            if measureSizeUpdated {
                measureSize(by: node,
                            availableDimensions: currDimensions.availableDimensions(by: node),
                            parentDimensions: parentDimensions)
            }
            computeNodesDimensions(by: node,
                                   parentDimensions: currDimensions,
                                   justForFlexBasis: !node.innerState.measuredDimensions!.isMeasuredExactly)
            let parentRenderOrigin = resovleBaseOrigin(by: node.innerState.parent!, forRelativeChildren: false)
            render(with: node,
                   origin: parentRenderOrigin.baseOrigin,
                   parentRenderOrigin: parentRenderOrigin,
                   scale: scale)
            // handle alignment
            let contentSize = node.innerState.measuredDimensions!.toSize()
            // if set horizontal and vertical dimensions, it means not allow to align
            if !((node.left.hasValue || node.right.hasValue)
                && (node.top.hasValue || node.bottom.hasValue)) {
                let (needUpdate, deltaX, deltaY) = resolveAlignmentDelta(by: node.innerState.parent!,
                                                                         contentSize: contentSize,
                                                                         forCrossAxis: nil)
                if needUpdate {
                    let frame = node.innerState.view!.frame
                    let x = frame.origin.x + CGFloat(deltaX)
                    let y = frame.origin.y + CGFloat(deltaY)
                    node.innerState.view!.frame = CGRect(x: x, y: y,
                                                         width: frame.size.width,
                                                         height: frame.size.height)
                }
            }
        } else if let parent = node.innerState.parent, parent.position == .absolute {
            if parent.innerState.resolvedDimensions == nil {
                renderAbsolute(with: parent, scale: scale)
            }
            renderAbsolute(with: node, scale: scale)
        }
    }
    
    private func resolveAlignmentDelta(by node: FLNode,
                                       contentSize: (Width: Float, Height: Float),
                                       forCrossAxis: Bool?) -> (Bool, Float, Float) {
        // TODO: handle cross line on main and alignContent condition. handle totol size on alignItems condition
        var needUpdate: Bool = false
        var deltaX: Float = 0
        var deltaY: Float = 0
        let size = node.innerState.measuredDimensions!.toSize()
        let innerSize = node.position == .relative ? node.innerState.measuredDimensions!.availableDimensions(by: node).innerSize(by: node) : size
        if forCrossAxis == nil || forCrossAxis! == false {
            if node.justify == .center {
                let mainInnerSize = node.isMainAxisRow ? innerSize.Width : innerSize.Height
                let mainContentSize = node.isMainAxisRow ? contentSize.Width : contentSize.Height
                if mainInnerSize != mainContentSize {
                    let delta = (mainInnerSize - mainContentSize) / 2
                    if node.isMainAxisRow {
                        deltaX = delta
                    } else {
                        deltaY = delta
                    }
                    needUpdate = true
                }
            }
        }
        if forCrossAxis == nil || forCrossAxis! == true {
            if (node.position == .relative
                && (node.alignItems == .center && node.wrap == .noWrap)
                    || (node.alignContent == .center && node.wrap != .noWrap))
                || (node.position == .absolute && node.alignItems == .center) {
                let crossSize = node.isMainAxisRow ? size.Height : size.Width
                let crossInnerSize = node.isMainAxisRow ? innerSize.Height : innerSize.Width
                let crossContentSize = node.isMainAxisRow ? contentSize.Height : contentSize.Width
                if crossInnerSize != crossContentSize {
                    let delta = crossInnerSize > crossContentSize ?
                                (crossInnerSize - crossContentSize) / 2 :
                                (crossSize - crossContentSize) / 2
                    if node.isMainAxisRow {
                        deltaY = delta
                    } else {
                        deltaX = delta
                    }
                    needUpdate = true
                }
            }
        }
        return (needUpdate, deltaX, deltaY)
    }

    private func resovleBaseOrigin(by node: FLNode, forRelativeChildren: Bool = true) -> FLRenderOrigin {
        var onOriginLeft = node.direction != .rowReverse
        var onOriginTop = node.direction != .columnReverse
        if forRelativeChildren
            && node.wrap != .noWrap {
            if node.isMainAxisRow {
                onOriginTop = node.wrap == .wrap
            } else {
                onOriginLeft = node.wrap == .wrap
            }
        }
        let measureSize = node.innerState.measuredDimensions!.toSize()
        var origin: (x: Float, y: Float) = (0, 0)
        if onOriginLeft && !onOriginTop {
            origin = (node.paddingLeft.value, measureSize.Height - node.paddingBottom.value)
        } else if !onOriginLeft && onOriginTop {
            origin = (measureSize.Width - node.paddingRight.value, node.paddingTop.value)
        } else if !onOriginLeft && !onOriginTop {
            origin = (measureSize.Width - node.paddingRight.value, measureSize.Height - node.paddingBottom.value)
        } else {
            origin = (node.paddingLeft.value, node.paddingTop.value)
        }
        return FLRenderOrigin(isMainAxisRow: node.isMainAxisRow,
                              onOriginLeft: onOriginLeft,
                              onOriginTop: onOriginTop,
                              measureSize: measureSize,
                              padding: (node.paddingLeft.value,
                                        node.paddingRight.value,
                                        node.paddingTop.value,
                                        node.paddingBottom.value),
                              baseOrigin: origin)
    }

    private func layout(with node: FLNode,
                        origin: (x: Float, y: Float),
                        renderOrigin: FLRenderOrigin) {
        let view = node.innerState.view!
        // if currAxix is main then point use origin, else is cross use renderOrigin
        var x: Float = renderOrigin.isMainAxisRow ? origin.x : renderOrigin.baseOrigin.x
        var y: Float = renderOrigin.isMainAxisRow ? renderOrigin.baseOrigin.y : origin.y
        let currBounds = node.innerState.measuredDimensions!.toSize()
        if renderOrigin.onOriginLeft {
            x += node.marginLeft.value + (node.position == .absolute ? node.left.value : 0)
            if node.position == .absolute
                && (!node.left.hasValue && node.right.hasValue) {
                x = renderOrigin.measureSize.Width - renderOrigin.padding.Right
                x += -node.marginRight.value - currBounds.Width - (node.position == .absolute ? node.right.value : 0)
            }
        } else {
            x += -node.marginRight.value - currBounds.Width - (node.position == .absolute ? node.right.value : 0)
            if node.position == .absolute
                && (!node.right.hasValue && node.left.hasValue) {
                x = renderOrigin.padding.Left
                x += node.marginLeft.value + (node.position == .absolute ? node.left.value : 0)
            }
        }
        if renderOrigin.onOriginTop {
            y += node.marginTop.value + (node.position == .absolute ? node.top.value : 0)
            if node.position == .absolute
                && (!node.top.hasValue && node.bottom.hasValue) {
                y = renderOrigin.measureSize.Height - renderOrigin.padding.Bottom
                y += -node.marginBottom.value - currBounds.Height - (node.position == .absolute ? node.bottom.value : 0)
            }
        } else {
            y += -node.marginBottom.value - currBounds.Height - (node.position == .absolute ? node.bottom.value : 0)
            if node.position == .absolute
                && (!node.bottom.hasValue && node.top.hasValue) {
                y = renderOrigin.padding.Top
                y += node.marginTop.value + (node.position == .absolute ? node.top.value : 0)
            }
        }
        view.frame = CGRect(x: x, y: y, width: currBounds.Width, height: currBounds.Height)
    }

    private func handleLeftBottomOrigin(by node: FLNode) {
        if let superView = node.innerState.view.superview {
            var needUpdate = true
            let parentBounds = superView.bounds
            if node.innerState.isRoot {
                let superViewType = type(of: superView)
                if superViewType.description() == "NSThemeFrame" {
                    needUpdate = false
                }
            }
            if needUpdate {
                let frame = node.innerState.view.frame
                let updateY = parentBounds.size.height - frame.origin.y - frame.size.height
                node.innerState.view.frame = CGRect(x: frame.origin.x,
                                                    y: updateY,
                                                    width: frame.size.width,
                                                    height: frame.size.height)
            }
        }
        
        for child in node.innerState.children {
            if child.display == .none {
                continue
            }
            handleLeftBottomOrigin(by: child)
        }
    }

    private func roundToPixelGrid(with frame: CGRect, scale: Float) -> CGRect {
        return roundToPixelGrid(x: Float(frame.origin.x),
                                y: Float(frame.origin.y),
                                width: Float(frame.size.width),
                                height: Float(frame.size.height),
                                scale: scale)
    }
    private func roundToPixelGrid(x: Float,
                                  y: Float,
                                  width: Float,
                                  height: Float,
                                  scale: Float) -> CGRect {
        let roundFn = { (value: Float) in
            roundf(value * scale) / scale
        }
        let updateX = roundFn(x)
        let updateY = roundFn(y)
        let updateWidth = roundFn(x + width) - updateX
        let updateHeight = roundFn(y + height) - updateY
        return CGRect(x: updateX, y: updateY, width: updateWidth, height: updateHeight)
    }
    
    // REMARK: handle measure methods
    private func computeNodesDimensions(by node: FLNode,
                                        parentDimensions: FLDimensions,
                                        justForFlexBasis: Bool = false) {
        var currDimensions = node.innerState.resolvedDimensions ??
                                resolveSize(by: node, parentDimensions: parentDimensions)
        if node.innerState.prepareToCompute
            || node.innerState.didMeasured {
            currDimensions = node.innerState.measuredDimensions!
        }
        if node.innerState.resolvedDimensions == nil {
            node.innerState.resolvedDimensions = currDimensions
        }
        if !node.innerState.isLeaf {
            let innerDimensions = currDimensions.availableDimensions(by: node)
                .availableInnerDimensions(by: node,
                                          parentDimensions: parentDimensions,
                                          boundAxisSizeFn: boundAxisSize)
            var updateDimensions: FLDimensions! = nil
            if node.innerState.updatedMeasureSizeByParent {
                updateDimensions = innerDimensions.copy()
                if let parent = node.innerState.parent {
                    if parent.isMainAxisRow {
                        updateDimensions.widthAxis.measure = .atMost
                    } else {
                        updateDimensions.heightAxis.measure = .atMost
                    }
                }
            }
            for child in node.innerState.relativeChildren {
                if child.innerState.resolvedDimensions == nil {
                    let childDimensions = resolveSize(by: child, parentDimensions: innerDimensions)
                    child.innerState.resolvedDimensions = childDimensions
                }
                if justForFlexBasis || node.innerState.initExactly {
                    computeParentMainBasis(by: child, parentDimensions: innerDimensions)
                } else if node.innerState.updatedMeasureSizeByParent {
                    computeParentMainBasis(by: child, parentDimensions: updateDimensions)
                }
            }
            computeFlexSize(by: node,
                            availableDimensions: innerDimensions,
                            justForFlexBasis: justForFlexBasis)
            // children items total content size support parent flexbasis
            if justForFlexBasis
                && !(node.innerState.isRoot || node.position == .absolute) {
                return
            }
            if let measuredDimensions = node.innerState.measuredDimensions,
                let computedSize = node.innerState.computedParentFlexSize,
                (node.innerState.isRoot || node.position == .absolute)
                && !node.innerState.prepareToCompute {
                node.innerState.prepareToCompute = true
                if !justForFlexBasis {
                    node.innerState.didMeasured = true
                }
                if measuredDimensions.widthAxis.measure == .atMost {
                    if measuredDimensions.widthAxis.dim.value < computedSize.Width {
                        measuredDimensions.widthAxis.dim = FLValue(computedSize.Width)
                        measuredDimensions.widthAxis.measure = .exactly
                    } else {
                        // TODO: check, contentView设置minHeight后没有起作用，目前看是因为没有考虑minHeight与computeSize.height的关系
                        print("====================== not update measure widthAxis")
                    }
                }
                if measuredDimensions.heightAxis.measure == .atMost {
                    if measuredDimensions.heightAxis.dim.value < computedSize.Height {
                        measuredDimensions.heightAxis.dim = FLValue(computedSize.Height)
                        measuredDimensions.heightAxis.measure = .exactly
                    } else {
//                        print("====================== not update measure heightAxis")
                    }
                }
            }
            for child in node.innerState.relativeChildren {
                computeNodesDimensions(by: child, parentDimensions: innerDimensions)
            }
        }
        if node.innerState.didMeasured && !node.innerState.prepareToCompute {
            node.innerState.prepareToCompute = true
        }
    }
    
    private func computeParentMainBasis(by node: FLNode, parentDimensions: FLDimensions) {
        var flexSize: (Width: Float, Height: Float)! = nil
        if node.innerState.computedParentFlexSize != nil {
            // second measure size, cause by parentDimensions chanaged
            let hasValues = node.innerState.measuredDimensions?.hasValues ?? false
            let needUpdate = !node.innerState.cachedDimensionsForComputeParentBasis!.equalSize(to: parentDimensions)
            if !node.innerState.computeParentBasisBySelf
                && hasValues && needUpdate {
                if !node.innerState.prepareToCompute {
                    let availableDimensions = parentDimensions.availableDimensions(by: node)
                    measureSize(by: node, availableDimensions: availableDimensions, parentDimensions: parentDimensions)
                    flexSize = node.innerState.measuredDimensions?.toSize()
                }
                node.innerState.cachedDimensionsForComputeParentBasis = parentDimensions
            }
            // TODO: check, 如果parentNode与childNode直接的关系解除(remove)，需要清空cachedDimensionsForComputeParentBasis
            if flexSize == nil {
//                print("========== warning ....")
                return
            }
        }
        node.innerState.cachedDimensionsForComputeParentBasis = parentDimensions
        var hasFlexSize = false
        if let measuredDimensions = node.innerState.measuredDimensions {
            flexSize = measuredDimensions.toSize()
            if measuredDimensions.widthAxis.measure == .atMost {
                if node.innerState.isLeaf {
                    measuredDimensions.widthAxis.measure = .exactly
                } else {
                    flexSize.Width = 0
                }
            }
            if measuredDimensions.heightAxis.measure == .atMost {
                if node.innerState.isLeaf {
                    measuredDimensions.heightAxis.measure = .exactly
                } else {
                    flexSize.Height = 0
                }
            }
            
            if !(flexSize.Width == 0 && flexSize.Height == 0) {
                node.innerState.computedParentFlexSize = flexSize
            } else if (measuredDimensions.isEmptyValues && node.innerState.isLeaf) {
                node.innerState.computedParentFlexSize = flexSize
            }
            hasFlexSize = flexSize.Width > 0 && flexSize.Height > 0
            if !node.innerState.didMeasured && measuredDimensions.isMeasuredExactly {
                node.innerState.didMeasured = true
            }
        }
        if !(node.innerState.isRoot
            || node.innerState.isLeaf
            || node.innerState.prepareToCompute
            || hasFlexSize) {
            computeNodesDimensions(by: node, parentDimensions: parentDimensions, justForFlexBasis: true)
        }
    }
    
    private func computeFlexSize(by node: FLNode,
                                 availableDimensions: FLDimensions,
                                 justForFlexBasis: Bool) {
        var crossLine = 0
        var crossDim: Float = 0
        var sizeConsumedOnCurrentLine: Float = 0
        var currComputeFlex = FLComputeFlex(crossLine: crossLine)
        var allComputeFlexs: [FLComputeFlex] = []
        var totalLineMainDim: Float = 0
        var totalLineCrossDim: Float = 0
        let isWrapMainContent = node.wrap != .noWrap
        let availableMainDim = node.isMainAxisRow ?
                                availableDimensions.widthAxis.dim.value :
                                availableDimensions.heightAxis.dim.value

        for child in node.innerState.relativeChildren {
            var outerFlexBasis: Float = 0
            var outerCrossDim: Float = 0
            // get measure size for crossDim
            var childMeasuredDimensions: FLDimensions = child.innerState.measuredDimensions!
            if let updateResolvedDimensions = child.innerState.resolvedDimensions,
                !child.innerState.prepareToCompute && !child.innerState.didMeasured {
                let updateSize = updateResolvedDimensions.toSize()
                var measuredSize = childMeasuredDimensions.toSize()
                if updateSize.Width > 0 && updateSize.Height > 0
                    && (updateSize.Width != measuredSize.Width
                        || updateSize.Height != measuredSize.Height) {
                    let currDimensions = updateResolvedDimensions.availableDimensions(by: child).copy()
                    currDimensions.widthAxis.measure = .exactly
                    currDimensions.heightAxis.measure = .exactly
                    measureSize(by: child,
                                availableDimensions: currDimensions,
                                parentDimensions: availableDimensions)
                    measuredSize = child.innerState.measuredDimensions!.toSize()
                    let computeSize = child.innerState.computedParentFlexSize!
                    child.innerState.computeParentBasisBySelf = true
                    child.innerState.cachedDimensionsForComputeParentBasis = currDimensions
                    if computeSize.Width != measuredSize.Width || computeSize.Height != measuredSize.Height {
                        child.innerState.computedParentFlexSize = measuredSize
                    }
                    childMeasuredDimensions = child.innerState.measuredDimensions!
                }
            }
            // get flex basic info
            if node.isMainAxisRow {
                outerFlexBasis = max(child.minWidth.value, (child.innerState.computedParentFlexSize!.Width + child.marginForAxisRow))
                if isWrapMainContent {
                    outerCrossDim = childMeasuredDimensions.heightAxis.dim.value + child.marginForAxisColumn
                } else {
                    crossDim = max(crossDim, childMeasuredDimensions.heightAxis.dim.value + child.marginForAxisColumn)
                }
                currComputeFlex.totalFlexShrinkScaledFactors += -child.flexShrink.value * child.innerState.computedParentFlexSize!.Width
                currComputeFlex.totalFlexGrowFactors += child.flexGrow.value
            } else {
                outerFlexBasis = max(child.minHeight.value, (child.innerState.computedParentFlexSize!.Height + child.marginForAxisColumn))
                if isWrapMainContent {
                     outerCrossDim = childMeasuredDimensions.widthAxis.dim.value + child.marginForAxisRow
                } else {
                    crossDim = max(crossDim, childMeasuredDimensions.widthAxis.dim.value + child.marginForAxisRow)
                }
                currComputeFlex.totalFlexShrinkScaledFactors += -child.flexShrink.value * child.innerState.computedParentFlexSize!.Height
                currComputeFlex.totalFlexGrowFactors += child.flexGrow.value
            }
            // handle wrap
            if isWrapMainContent {
                // will begin next crossLine
                if sizeConsumedOnCurrentLine + outerFlexBasis > availableMainDim {
                    currComputeFlex.remainingFreeSpace = availableMainDim - sizeConsumedOnCurrentLine
                    currComputeFlex.crossContentDim = crossDim
                    allComputeFlexs.append(currComputeFlex)
                    totalLineCrossDim += crossDim

                    crossLine += 1
                    currComputeFlex = FLComputeFlex(crossLine: crossLine)
                    sizeConsumedOnCurrentLine = 0
                    crossDim = outerCrossDim
                } else {
                    crossDim = max(crossDim, outerCrossDim)
                }
                child.innerState.crossLine = crossLine
            }
            sizeConsumedOnCurrentLine += outerFlexBasis
        }
        // for noWrap or last wrap line
        currComputeFlex.remainingFreeSpace = availableMainDim - sizeConsumedOnCurrentLine
        currComputeFlex.crossContentDim = crossDim
        allComputeFlexs.append(currComputeFlex)
        totalLineCrossDim += crossDim

        // first pass
        currComputeFlex = allComputeFlexs[0]
        node.innerState.children.first?.innerState.crossLineComputedFlex = currComputeFlex
        for child in node.innerState.relativeChildren {
            var baseMainSize: Float = 0
            var boundMainSize: Float = 0
            let currCrossLine = child.innerState.crossLine
            if currComputeFlex.crossLine != currCrossLine {
                currComputeFlex = allComputeFlexs[currCrossLine]
                currComputeFlex.totalFlexShrinkScaledFactors += currComputeFlex.deltaFlexShrinkScaledFactors;
                currComputeFlex.totalFlexGrowFactors += currComputeFlex.deltaFlexGrowFactors;
                currComputeFlex.remainingFreeSpace += currComputeFlex.deltaFreeSpace
                currComputeFlex.deltaFreeSpace = 0
                child.innerState.crossLineComputedFlex = currComputeFlex
            }
            currComputeFlex.childFlexBasis = node.isMainAxisRow ? child.innerState.computedParentFlexSize!.Width : child.innerState.computedParentFlexSize!.Height
            if currComputeFlex.remainingFreeSpace < 0 {
                currComputeFlex.flexShrinkScaledFactor = -child.flexShrink.value * currComputeFlex.childFlexBasis
                if currComputeFlex.flexShrinkScaledFactor != 0 {
                    baseMainSize =
                        currComputeFlex.childFlexBasis +
                        currComputeFlex.remainingFreeSpace / currComputeFlex.totalFlexShrinkScaledFactors * currComputeFlex.flexShrinkScaledFactor;
                    let boundSize = boundAxisSize(withNode: child,
                                                  availableSize: (
                                                    Width: node.isMainAxisRow ? baseMainSize : 0,
                                                    Height: !node.isMainAxisRow ? baseMainSize : 0),
                                                  parentSize: availableDimensions.toSize())
                    boundMainSize = node.isMainAxisRow ? boundSize.Width : boundSize.Height
                    if (baseMainSize != boundMainSize) {
                      currComputeFlex.deltaFreeSpace -= boundMainSize - currComputeFlex.childFlexBasis;
                      currComputeFlex.deltaFlexShrinkScaledFactors -= currComputeFlex.flexShrinkScaledFactor;
                    }
                }
            } else if !justForFlexBasis && currComputeFlex.remainingFreeSpace > 0 {
                currComputeFlex.flexGrowFactor = child.flexGrow.value
                if currComputeFlex.flexGrowFactor != 0 {
                    baseMainSize =
                        currComputeFlex.childFlexBasis + currComputeFlex.remainingFreeSpace / currComputeFlex.totalFlexGrowFactors * currComputeFlex.flexGrowFactor
                    let boundSize = boundAxisSize(withNode: child,
                                                  availableSize: (
                                                    Width: node.isMainAxisRow ? baseMainSize : 0,
                                                    Height: !node.isMainAxisRow ? baseMainSize : 0),
                                                  parentSize: availableDimensions.toSize())
                    boundMainSize = node.isMainAxisRow ? boundSize.Width : boundSize.Height
                    if (baseMainSize != boundMainSize) {
                      currComputeFlex.deltaFreeSpace -= boundMainSize - currComputeFlex.childFlexBasis;
                      currComputeFlex.deltaFlexGrowFactors -= currComputeFlex.flexGrowFactor;
                    }
                }                
            }
        }
        // for noWrap or last wrap line
        currComputeFlex.totalFlexShrinkScaledFactors += currComputeFlex.deltaFlexShrinkScaledFactors;
        currComputeFlex.totalFlexGrowFactors += currComputeFlex.deltaFlexGrowFactors;
        currComputeFlex.remainingFreeSpace += currComputeFlex.deltaFreeSpace
        currComputeFlex.deltaFreeSpace = 0
        // second pass
        sizeConsumedOnCurrentLine = 0
        currComputeFlex = allComputeFlexs[0]
        for child in node.innerState.relativeChildren {
            let currCrossLine = child.innerState.crossLine
            if currComputeFlex.crossLine != currCrossLine {
                currComputeFlex.mainContentDim = sizeConsumedOnCurrentLine

                currComputeFlex = allComputeFlexs[currCrossLine]
                totalLineMainDim = max(totalLineMainDim, sizeConsumedOnCurrentLine)
                sizeConsumedOnCurrentLine = 0
            }
            currComputeFlex.childFlexBasis = node.isMainAxisRow ? child.innerState.computedParentFlexSize!.Width : child.innerState.computedParentFlexSize!.Height
            var updatedMainSize = currComputeFlex.childFlexBasis
            if currComputeFlex.remainingFreeSpace < 0 {
                currComputeFlex.flexShrinkScaledFactor = -child.flexShrink.value * currComputeFlex.childFlexBasis
                if currComputeFlex.flexShrinkScaledFactor != 0 {
                    var childSize: Float = 0
                    if currComputeFlex.totalFlexShrinkScaledFactors == 0 {
                        childSize = currComputeFlex.childFlexBasis + currComputeFlex.flexShrinkScaledFactor
                    } else {
                        childSize =
                            currComputeFlex.childFlexBasis +
                            (currComputeFlex.remainingFreeSpace / currComputeFlex.totalFlexShrinkScaledFactors) * currComputeFlex.flexShrinkScaledFactor
                    }
                    let boundSize = boundAxisSize(withNode: child,
                                                  availableSize: (
                                                    Width: node.isMainAxisRow ? childSize : 0,
                                                    Height: !node.isMainAxisRow ? childSize : 0),
                                                  parentSize: availableDimensions.toSize())
                    updatedMainSize = node.isMainAxisRow ? boundSize.Width : boundSize.Height
                }
            } else if !justForFlexBasis && currComputeFlex.remainingFreeSpace > 0 {
                currComputeFlex.flexGrowFactor = child.flexGrow.value
                if currComputeFlex.flexGrowFactor != 0 {
                    let childSize = currComputeFlex.childFlexBasis +
                                        currComputeFlex.remainingFreeSpace / currComputeFlex.totalFlexGrowFactors * currComputeFlex.flexGrowFactor
                    let boundSize = boundAxisSize(withNode: child,
                                                  availableSize: (
                                                    Width: node.isMainAxisRow ? childSize : 0,
                                                    Height: !node.isMainAxisRow ? childSize : 0),
                                                  parentSize: availableDimensions.toSize())
                    updatedMainSize = node.isMainAxisRow ? boundSize.Width : boundSize.Height
                }
            }
            let childMainSize = updatedMainSize + (node.isMainAxisRow ? child.marginForAxisRow : child.marginForAxisColumn)
            sizeConsumedOnCurrentLine += childMainSize
            let childMeasuredDimensions = child.innerState.measuredDimensions!
            if node.isMainAxisRow {
                if (child.flexShrink.value > 0 && childMeasuredDimensions.widthAxis.dim.value > updatedMainSize)
                    || (child.flexGrow.value > 0 && childMeasuredDimensions.widthAxis.dim.value < updatedMainSize) {
                    child.innerState.measuredDimensions?.widthAxis.dim = FLValue(updatedMainSize)
                    child.innerState.computedParentFlexSize = childMeasuredDimensions.toSize()
                    if !justForFlexBasis {
                        child.innerState.updatedMeasureSizeByParent = true
                        if child.innerState.measuredDimensions!.widthAxis.measure == .atMost {
                            child.innerState.measuredDimensions!.widthAxis.measure = .exactly
                            child.innerState.didMeasured = true
                        #if os(OSX)
//                            print("[DEBUG] setExactlyByGrow for \(child.innerState.view.flTag)")
                        #endif
                        }
                    }
                }
            } else {
                if (child.flexShrink.value > 0 && childMeasuredDimensions.heightAxis.dim.value > updatedMainSize)
                    || (child.flexGrow.value > 0 && childMeasuredDimensions.heightAxis.dim.value < updatedMainSize) {
                    child.innerState.measuredDimensions?.heightAxis.dim = FLValue(updatedMainSize)
                    child.innerState.computedParentFlexSize = childMeasuredDimensions.toSize()
                    if !justForFlexBasis {
                        child.innerState.updatedMeasureSizeByParent = true
                        if child.innerState.measuredDimensions!.heightAxis.measure == .atMost {
                            child.innerState.measuredDimensions!.heightAxis.measure = .exactly
                            child.innerState.didMeasured = true
                        #if os(OSX)
//                            print("[DEBUG] setExactlyByGrow for \(child.innerState.view.flTag)")
                        #endif
                        }
                    }
                }
            }

            // detech child need stretch, add at 12.12
            if !justForFlexBasis && node.wrap == .noWrap && node.alignItems == .stretch,
                let nodeDimensions = node.innerState.measuredDimensions,
                let childMeasureDimensions = child.innerState.measuredDimensions,
                let childResolveDimensions = child.innerState.resolvedDimensions {
                let childMeasureSameAxis = childMeasureDimensions.axisSameParent(axisDirection: nodeDimensions.crossAxis.axisDirection)
                let childResolveSameAxis = childResolveDimensions.axisSameParent(axisDirection: nodeDimensions.crossAxis.axisDirection)
                if childResolveSameAxis.measure == .atMost {
                    // TODO: remove
                    let oldDim = childMeasureSameAxis.dim.value
                    
                    if nodeDimensions.setAlignStretch(forceUpdate: true,
                                                      for: child,
                                                      childAvailableDimensions: childMeasureDimensions,
                                                      boundAxisSizeFn: boundAxisSize) {
                        if !child.innerState.didMeasured {
                            if childMeasureDimensions.widthAxis.measure == .atMost {
                                childMeasureDimensions.widthAxis.measure = .exactly
                            }
                            if childMeasureDimensions.heightAxis.measure == .atMost {
                                childMeasureDimensions.heightAxis.measure = .exactly
                            }
                            child.innerState.didMeasured = true
                            child.innerState.cachedDimensionsForComputeParentBasis = node.innerState.measuredDimensions
                            child.innerState.computedParentFlexSize = child.innerState.measuredDimensions!.toSize()
                        }
                        
                        // TODO: remove
                        let newDim = childMeasureSameAxis.dim.value
                    #if os(OSX)
//                        print("[DEBUG] updateByStretch for \(child.innerState.view.flTag), oldDim: \(oldDim), newDim: \(newDim)")
                    #endif
                    }
                }
            }
        }
        // for noWrap or last wrap line
        currComputeFlex.mainContentDim = sizeConsumedOnCurrentLine
        totalLineMainDim = max(totalLineMainDim, sizeConsumedOnCurrentLine)
        totalLineMainDim += node.isMainAxisRow ? node.paddingForAxisRow : node.paddingForAxisColumn
        totalLineCrossDim += node.isMainAxisRow ? node.paddingForAxisColumn : node.paddingForAxisRow
        // compute node flex and update cross dim
        var sameToParentMainAxis = node.innerState.resolvedDimensions!.mainAxis
        if let parent = node.innerState.parent {
            sameToParentMainAxis = node.innerState.resolvedDimensions!.axisSameParent(axisDirection: parent.mainAxisDirection)
        }
        let sameToParentCrossAxis = sameToParentMainAxis.isRowDirection ?
            node.innerState.resolvedDimensions!.heightAxis :
            node.innerState.resolvedDimensions!.widthAxis
        var sameToParentMainAxisDim = totalLineMainDim
        var sameToParentCrossAxisDim = totalLineCrossDim
        if sameToParentMainAxis.isRowDirection != node.isMainAxisRow {
            sameToParentMainAxisDim = totalLineCrossDim
            sameToParentCrossAxisDim = totalLineMainDim
        }
        let needUpdateMainDim = sameToParentMainAxis.measure != .exactly
                                && sameToParentMainAxis.dim.value != sameToParentMainAxisDim
        // check content is wraped
        var isContentWraped = false
        if node.wrap != .noWrap {
            for child in node.innerState.children {
                if child.innerState.crossLine > 0 {
                    isContentWraped = true
                    break
                }
            }
        }
        if node.innerState.computedParentFlexSize == nil
           || needUpdateMainDim {
            if needUpdateMainDim {
                // it means in wrap mode and parentMainAxis direction same to wrap direction
                if isContentWraped
                    && sameToParentMainAxis.isRowDirection == node.isMainAxisRow {
                    let availableCrossSameToParentDim = sameToParentMainAxis.isRowDirection ?
                                    (availableDimensions.widthAxis.dim.value + node.paddingForAxisRow) :
                                    (availableDimensions.heightAxis.dim.value + node.paddingForAxisColumn)
                    sameToParentMainAxis.dim = FLValue(max(sameToParentMainAxisDim, availableCrossSameToParentDim))
                    sameToParentMainAxisDim = availableCrossSameToParentDim
                } else {
                    // it means children suppor node content dimension
                    sameToParentMainAxis.dim = FLValue(sameToParentMainAxisDim)
                }
            }
            node.innerState.computedParentFlexSize = (
                Width: sameToParentMainAxis.isRowDirection ? sameToParentMainAxisDim : sameToParentCrossAxisDim,
                Height: sameToParentMainAxis.isRowDirection ? sameToParentCrossAxisDim : sameToParentMainAxisDim
            )
        }
        if sameToParentCrossAxis.measure != .exactly {
            // it means is wrap and parentCrossAxis direction same to wrap direction
            if isContentWraped
                && sameToParentCrossAxis.isRowDirection == node.isMainAxisRow {
                let availableCrossSameToParentDim = sameToParentCrossAxis.isRowDirection ?
                                            (availableDimensions.widthAxis.dim.value + node.paddingForAxisRow) :
                                            (availableDimensions.heightAxis.dim.value + node.paddingForAxisColumn)
                sameToParentCrossAxis.dim = FLValue(max(sameToParentCrossAxisDim, availableCrossSameToParentDim))
            } else {
                // it means children suppor node content dimension
                sameToParentCrossAxis.dim = FLValue(sameToParentCrossAxisDim)
            }
        }
        if let nodeComputedFlex = node.innerState.crossLineComputedFlex {
            if nodeComputedFlex.mainContentDim < sameToParentMainAxis.dim.value {
                nodeComputedFlex.mainContentDim = sameToParentMainAxis.dim.value
            }
            if nodeComputedFlex.crossContentDim < sameToParentCrossAxis.dim.value {
                nodeComputedFlex.crossContentDim = sameToParentCrossAxis.dim.value
            }
        }
    }
    
    private func resolveSize(by node: FLNode, parentDimensions: FLDimensions) -> FLDimensions {
        // resolve dimensions
        var width: FLValue = .undefined
        var height: FLValue = .undefined
        var widthMeasure: FLMeasure = .undefined
        var heightMeasure: FLMeasure = .undefined
        let maxWidthValue: Float = parentDimensions.toChild(value: node.maxWidth).value
        let minWidthValue: Float = parentDimensions.toChild(value: node.minWidth).value
        let maxHeightValue: Float = parentDimensions.toChild(value: node.maxHeight, forWidth: false).value
        let minHeightValue: Float = parentDimensions.toChild(value: node.minHeight, forWidth: false).value
        
        let currWidth = node.maxWidth == node.minWidth ? node.maxWidth : node.width
        if currWidth.hasValue {
            width = parentDimensions.toChild(value: currWidth)
            widthMeasure = .exactly
        } else if parentDimensions.widthAxis.dim.hasValue {
            width = FLValue(max(parentDimensions.widthAxis.dim.value, node.paddingForAxisRow))
            widthMeasure = .atMost
            if node.innerState.isRoot
                && parentDimensions.widthAxis.measure == .exactly {
                widthMeasure = .exactly
            }
        }

        let currHeight = node.maxWidth == node.minHeight ? node.maxHeight : node.height
        if currHeight.hasValue {
            height = parentDimensions.toChild(value: node.height, forWidth: false)
            heightMeasure = .exactly
        } else if parentDimensions.heightAxis.dim.hasValue {
            height = FLValue(max(parentDimensions.heightAxis.dim.value, node.paddingForAxisRow))
            heightMeasure = .atMost
            if node.innerState.isRoot
                && parentDimensions.heightAxis.measure == .exactly {
                heightMeasure = .exactly
            }
        }
        // handle max dimension
        if node.maxWidth.hasValue {
            if widthMeasure == .undefined {
                width = FLValue(maxWidthValue)
                widthMeasure = .atMost
            } else if width.value > maxWidthValue {
                width = FLValue(maxWidthValue)
            }
        }
        if node.minWidth.hasValue && width.hasValue {
            width = FLValue(max(minWidthValue, width.value))
        }
        
        if node.maxHeight.hasValue {
            if heightMeasure == .undefined {
                height = FLValue(maxHeightValue)
                heightMeasure = .atMost
            } else if height.value > maxHeightValue {
                height = FLValue(maxHeightValue)
            }
        }
        if node.minHeight.hasValue && height.hasValue {
            height = FLValue(max(minHeightValue, height.value))
        }
        
        let currDimensions = FLDimensions(direction: node.direction,
                                          width: width,
                                          widthMeasure: widthMeasure,
                                          height: height,
                                          heightMeasure: heightMeasure,
                                          alignItems: node.alignItems,
                                          wrap: node.wrap)
        // stetch alignItems
        if node.position == .relative && parentDimensions.wrap == .noWrap
            && !(node.innerState.view.isFLContentNode && node.alignSelf != .stretch) {
            parentDimensions.setAlignStretch(for: node,
                                             childAvailableDimensions: currDimensions,
                                             boundAxisSizeFn: boundAxisSize)
        }
        if currDimensions.isMeasuredExactly {
            node.innerState.measuredDimensions = currDimensions
            node.innerState.initExactly = true
            node.innerState.didMeasured = true
            node.innerState.prepareToCompute = true
            node.innerState.computeParentBasisBySelf = true
            node.innerState.cachedDimensionsForComputeParentBasis = currDimensions
            node.innerState.computedParentFlexSize = currDimensions.toSize()
        } else {
            // add at 11.06
            measureSize(by: node,
                        availableDimensions: currDimensions.availableDimensions(by: node),
                        parentDimensions: parentDimensions)
            if (node.innerState.view.isFLContentNode && node.alignSelf != .stretch)
                && node.position == .relative && parentDimensions.wrap == .noWrap {
                parentDimensions.setAlignStretch(for: node,
                                                 childAvailableDimensions: currDimensions,
                                                 boundAxisSizeFn: boundAxisSize)
            }
        }
        return currDimensions
    }
  
    private func measureSize(by node: FLNode,
                             availableDimensions: FLDimensions,
                             parentDimensions: FLDimensions) {
        var boundSize: (Width: Float, Height: Float) = (0, 0)
        let measureMode = availableDimensions.measureMode()
        let availableSize = availableDimensions.toSize()
        let innerSize = availableDimensions.innerSize(by: node)
        
        if availableDimensions.widthAxis.measure == .exactly
            && availableDimensions.heightAxis.measure == .exactly {
            let availableSize = (
                Width: availableSize.Width - node.marginForAxisRow,
                Height: availableSize.Height - node.marginForAxisColumn
            )
            boundSize = boundAxisSize(withNode: node,
                                      availableSize: availableSize,
                                      parentSize: parentDimensions.toSize())
        } else if innerSize.Width <= 0 || innerSize.Height <= 0 {
            boundSize = boundAxisSize(withNode: node,
                                      availableSize: (0, 0),
                                      parentSize: availableDimensions.toSize())
        } else {
            let currDimensions = FLDimensions(width: FLValue(innerSize.Width),
                                              widthMeasure: measureMode.Width,
                                              height: FLValue(innerSize.Height),
                                              heightMeasure: measureMode.Height)
            let measuredSize = measureIntrinsicSize(by: node,
                                                    availableDimensions: currDimensions)
            let currSize = (
                Width: measureMode.Width == .exactly ?
                    (availableSize.Width - node.marginForAxisRow) :
                    (measuredSize.Width + node.paddingForAxisRow),
                Height: measureMode.Height == .exactly ?
                    (availableSize.Height - node.marginForAxisColumn) :
                    (measuredSize.Height + node.paddingForAxisColumn)
            )
            boundSize = boundAxisSize(withNode: node,
                                      availableSize: currSize,
                                      parentSize: availableDimensions.toSize())
        }
        let measureDimensions = FLDimensions(direction: node.direction,
                                             width: FLValue(boundSize.Width),
                                             widthMeasure: availableDimensions.widthAxis.measure,
                                             height: FLValue(boundSize.Height),
                                             heightMeasure: availableDimensions.heightAxis.measure,
                                             alignItems: node.alignItems)
        node.innerState.measuredDimensions = measureDimensions
        if measureDimensions.isMeasuredExactly {
            // TODO: remove
            if node.innerState.measuredDimensions!.isEmptyValues {
//                print("[DEBUG] find measure invalid value ...")
            }
            node.innerState.didMeasured = true
            if node.innerState.computedParentFlexSize != nil {
                node.innerState.prepareToCompute = true
            }
        }
    }
    
    private func measureIntrinsicSize(by node: FLNode,
                                      availableDimensions: FLDimensions) -> (Width: Float, Height: Float) {
        let size = node.innerState.view.sizeThatFits(CGSize(width: availableDimensions.widthAxis.dim.value,
                                                            height: availableDimensions.heightAxis.dim.value))
        if size == CGSize.zero
            && !node.innerState.isLeaf {
            return (0, 0)
        } else {
            // default is .exactly
            var width = availableDimensions.widthAxis.dim.value
            var height = availableDimensions.heightAxis.dim.value

            if availableDimensions.widthAxis.measure == .atMost {
                width = min(width, Float(size.width))
            } else if availableDimensions.widthAxis.measure == .undefined {
                width = Float(size.width)
            }

            if availableDimensions.heightAxis.measure == .atMost {
                height = min(height, Float(size.height))
            } else if availableDimensions.heightAxis.measure == .undefined {
                height = Float(size.height)
            }
            return (width, height)
        }
    }
    
    private func boundAxisSize(withNode: FLNode,
                               availableSize:(Width: Float, Height: Float),
                               parentSize: (Width: Float, Height: Float)) -> (Width: Float, Height: Float) {
        var width: Float = availableSize.Width
        var height: Float = availableSize.Height
        var minWidth: Float = 0
        if withNode.minWidth.hasValue {
            minWidth = withNode.minWidth.value * (withNode.minWidth.isNumeric ? 1 : parentSize.Width)
            if width < minWidth {
                width = minWidth
            }
        }
        var maxWidth: Float = 0
        if withNode.maxWidth.hasValue {
            maxWidth = withNode.maxWidth.value * (withNode.maxWidth.isNumeric ? 1 : parentSize.Width)
            if width > maxWidth {
                width = maxWidth
            }
        }
        var minHeight: Float = 0
        if withNode.minHeight.hasValue {
            minHeight = withNode.minHeight.value * (withNode.minHeight.isNumeric ? 1 : parentSize.Height)
            if height < minHeight {
                height = minHeight
            }
        }
        var maxHeight: Float = 0
        if withNode.maxHeight.hasValue {
            maxHeight = withNode.maxHeight.value * (withNode.maxHeight.isNumeric ? 1 : parentSize.Height)
            if height > maxHeight {
                height = maxHeight
            }
        }
        // why use axisRowPadding on height?
        return (max(width, withNode.paddingForAxisRow), max(height, withNode.paddingForAxisColumn))
    }
}
